/**
 * 
 */
package org.yelongframework.model.support.mybatis.service;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.yelongframework.model.Modelable;
import org.yelongframework.model.manager.ModelAndTable;
import org.yelongframework.model.support.mybatis.mapper.GenericMapperParamBuilder;
import org.yelongframework.model.support.mybatis.mapping.statement.MappedStatementBuilder;
import org.yelongframework.model.support.mybatis.sql.executor.MybatisSqlExecutor;
import org.yelongframework.model.support.spring.jdbc.TransactionalSqlModelService;
import org.yelongframework.mybatis.mapper.GenericMapper;
import org.yelongframework.sql.bound.SqlBound;
import org.yelongframework.sql.fragment.select.SelectSqlFragment;

/**
 * Mybatis模型服务实现
 * 
 * @since 1.0.0
 */
public abstract class AbstractMybatisModelService extends TransactionalSqlModelService implements MybatisModelService {

	/** 映射声明ID前缀 */
	public static final String STATEMENT_ID_PREFIX = "org.yelongframework.";

	// ==================================================构造器==================================================

	public AbstractMybatisModelService(MybatisModelServiceConfiguration mybatisModelServiceConfiguration) {
		super(mybatisModelServiceConfiguration);
	}

	// ==================================================执行==================================================

	@Override
	public <M extends Modelable> List<M> execute(Class<M> modelClass, SelectSqlFragment selectSqlFragment) {
		SqlBound boundSql = getPageHandler().pageHandleIf(getSqlDialect(), selectSqlFragment);
		GenericMapperParamBuilder genericMapperParamBuilder = getMybatisModelServiceConfiguration()
				.getGenericMapperParamBuilder();
		Map<String, Object> params = genericMapperParamBuilder.build(boundSql.getSql(), boundSql.getParams());
		ModelAndTable modelAndTable = getModelConfiguration().getModelAndTable(modelClass);
		SqlSession sqlSession = getSqlSession();
		Configuration configuration = sqlSession.getConfiguration();
		String statementId = getStatementId(modelClass);
		addMappedStatement(statementId, modelAndTable, configuration);
		return sqlSession.selectList(statementId, params);
	}

	// ==================================================映射声明==================================================

	/**
	 * 添加映射声明
	 * 
	 * @param statementId   声明ID
	 * @param modelAndTable 模型表
	 * @param configuration 配置
	 */
	protected void addMappedStatement(String statementId, ModelAndTable modelAndTable, Configuration configuration) {
		if (!configuration.hasStatement(statementId)) {
			synchronized (this) {
				if (!configuration.hasStatement(statementId)) {
					SqlSource sqlSource = defaultSelectModelSqlSource(configuration);
					MappedStatement mappedStatement = getMappedStatementBuilder().buildSelect(statementId,
							modelAndTable, sqlSource, configuration);
					configuration.addMappedStatement(mappedStatement);
				}
			}
		}
	}

	/**
	 * @return 映射声明构建器
	 */
	protected MappedStatementBuilder getMappedStatementBuilder() {
		return getMybatisModelServiceConfiguration().getMappedStatementBuilder();
	}

	/**
	 * 默认的SQL源这仅针对model的查询操作
	 * 
	 * @param configuration 配置
	 * @return 默认SQL源
	 */
	protected SqlSource defaultSelectModelSqlSource(Configuration configuration) {
		LanguageDriver languageDriver = configuration.getLanguageDriver(null);
		SqlSource sqlSource = languageDriver.createSqlSource(configuration, GenericMapper.SQL, Map.class);
		return sqlSource;
	}

	/**
	 * 获取生成的ID。这个ID为mybatis中获取SQL唯一的标识。且这个标识也应用到了日志中。
	 * 
	 * @param modelClass 模型类型
	 * @return 声明ID
	 */
	protected String getStatementId(Class<? extends Modelable> modelClass) {
		// 以org.yelongframework开头在日志设置mapper包目录时设置为 org.yelongframework 即可
		return STATEMENT_ID_PREFIX + modelClass.getName() + ".select";
	}

	// ==================================================support==================================================

	@Override
	public SqlSession getSqlSession() {
		return getMybatisSqlExecutor().getSqlSession();
	}

	@Override
	public MybatisSqlExecutor getMybatisSqlExecutor() {
		return getMybatisModelServiceConfiguration().getMybatisSqlExecutor();
	}

	@Override
	public MybatisModelServiceConfiguration getMybatisModelServiceConfiguration() {
		return (MybatisModelServiceConfiguration) getModelServiceConfiguration();
	}

}
